package app

import (
	"bufio"
	"cade/src/bot/msg"
	"cade/src/core"
	"cade/src/core/evtmgr"
	"cade/src/core/log"
	"cade/src/core/packet"
	Err "cade/src/proto/errorcode"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"
)

// ============================================================================

type Bot struct {
	Id          int32
	Name        string
	sock        socket_t
	preader     *packet.Reader
	pwriter     *packet.Writer
	locker_send sync.Mutex

	// msgq
	msgq chan func()
	quit chan bool
}

// ============================================================================

func new_bot(sock socket_t, id int32, name string) *Bot {
	bot := &Bot{
		Id:      id,
		Name:    name,
		sock:    sock,
		preader: packet.NewReader(),
		pwriter: packet.NewWriter(),

		msgq: make(chan func(), 1000),
		quit: make(chan bool),
	}

	// bot.preader.SetDecryptor(NewRc4())
	// bot.pwriter.SetEncryptor(NewRc4())

	return bot
}

func (self *Bot) SendPacket(p packet.Packet) {
	self.locker_send.Lock()
	defer self.locker_send.Unlock()

	buf := self.pwriter.Write(p)
	self.sock.Send(buf)
}

func (self *Bot) SendMsg(message msg.Message) {
	body, err := msg.Marshal(message)
	if err != nil {
		log.Error("marshal msg failed:", message.MsgId(), err)
		return
	}

	p := packet.Assemble(message.MsgId(), body)

	self.SendPacket(p)
}

func (self *Bot) Close() {
	self.sock.Close()
}

func (self *Bot) Dispatch(p packet.Packet) {
	// !Note: in net-thread

	op := p.Op()
	f := msg.MsgCreators[op]
	if f != nil {
		message := f()
		err := msg.Unmarshal(p.Body(), message)
		if err != nil {
			log.Warning("unmarshal msg failed:", err)
			return
		}

		h := msg.MsgHandlers[op]
		if h != nil {
			self.Push(func() {
				h(message, self)
			})
		}

		// fire
		self.Push(func() {
			evtmgr.Fire(fmt.Sprintf("msg.%d", op), self, message)
		})
	}
}

// ============================================================================

func (self *Bot) Push(f func()) {
	defer func() { recover() }()

	select {
	case self.msgq <- f:
	default:
		log.Error("msgq FULL. push discarded")
	}
}

func (self *Bot) run() {
	go func() {
		defer close(self.msgq)

		for {
			select {
			case <-self.quit:
				return

			case f := <-self.msgq:
				core.PCall(f)
			}
		}
	}()
}

func (self *Bot) stop() {
	close(self.quit)
}

// ============================================================================

func (self *Bot) start_console() {
	// heartbeat thread
	core.Go(func() {
		for {
			select {
			case <-self.quit:
				return

			default:
				time.Sleep(time.Second * 15)
				self.SendMsg(&msg.C_TimeSync{})
			}
		}
	})

	// console thread
	core.Go(func() {
		rd := bufio.NewReader(os.Stdin)

		log.Warning("type 'help' to see all commands")
		for true {
			// read line
			line, err := rd.ReadString('\n')
			if err != nil {
				log.Warning("stdin ENDED! you MUST end the program by pressing Ctrl+C")
				core.Panic(err)
			}
			line = strings.TrimSpace(line)
			log.Critical(line)

			// parse cmd
			cmd := parse_bot_cmd(line)

			// execute
			self.Push(func() {
				cmd.execute(self)
			})
		}
	})
}

// ============================================================================

func (self *Bot) OnConnected() {
	log.Info("bot connected:", self.Id)

	self.SendMsg(&msg.C_Login{
		Name: self.Name,
	})
}

func (self *Bot) OnDisconnected() {
	log.Info("bot disconnected:", self.Id)
}

func (self *Bot) OnLogin(req *msg.GW_Login_R) {
	if req.ErrorCode != Err.OK {
		log.Error("bot login failed:", self.Id, "ErrorCode:", req.ErrorCode)
		self.Close()
		return
	}
}

func (self *Bot) OnUserInfo(user *msg.GS_UserInfo) {
	log.Info("user info:", user.UserId, user.UserName)

	// fire
	evtmgr.Fire("userinfo", self, user)

	// start console
	self.start_console()
}
